/*******************************************************************************
 * Copyright (c) 2009 Luaj.org. All rights reserved.
 * <p/>
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * <p/>
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * <p/>
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 ******************************************************************************/
package org.luaj.vm2.lib;

//TODO 与3.0不一样

import android.util.Log;

import com.taobao.luaview.util.LogUtil;

import java.io.IOException;
import java.io.InputStream;

import org.luaj.vm2.Globals;
import org.luaj.vm2.Lua;
import org.luaj.vm2.LuaError;
import org.luaj.vm2.LuaString;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaThread;
import org.luaj.vm2.LuaValue;
import org.luaj.vm2.Varargs;

/**
 * Subclass of {@link LibFunction} which implements the lua basic library functions.
 * <p/>
 * This contains all library functions listed as "basic functions" in the lua documentation for JME.
 * The functions dofile and loadfile use the
 * {@link #finder} instance to find resource files.
 * Since JME has no file system by default, {@link BaseLib} implements
 * {@link ResourceFinder} using {@link Class#getResource(String)},
 * which is the closest equivalent on JME.
 * The default loader chain in {@link PackageLib} will use these as well.
 * <p/>
 * To use basic library functions that include a {@link ResourceFinder} based on
 * directory lookup, use {@link JseBaseLib} instead.
 * <p/>
 * Typically, this library is included as part of a call to either
 * {@link JsePlatform#standardGlobals()} or
 * {@link JmePlatform#standardGlobals()}
 * <pre> {@code
 * Globals globals = JsePlatform.standardGlobals();
 * globals.get("print").call(LuaValue.valueOf("hello, world"));
 * } </pre>
 * <p/>
 * For special cases where the smallest possible footprint is desired,
 * a minimal set of libraries could be loaded
 * directly via {@link Globals#load(LuaValue)} using code such as:
 * <pre> {@code
 * Globals globals = new Globals();
 * globals.load(new JseBaseLib());
 * globals.get("print").call(LuaValue.valueOf("hello, world"));
 * } </pre>
 * Doing so will ensure the library is properly initialized
 * and loaded into the globals table.
 * <p/>
 * This is a direct port of the corresponding library in C.
 *
 * @see JseBaseLib
 * @see ResourceFinder
 * @see #finder
 * @see LibFunction
 * @see JsePlatform
 * @see JmePlatform
 * @see <a href="http://www.lua.org/manual/5.2/manual.html#6.1">Lua 5.2 Base Lib Reference</a>
 */
public class BaseLib extends TwoArgFunction implements ResourceFinder {

    Globals globals;

    public LuaValue call(LuaValue modname, LuaValue env) {
        globals = env.checkglobals();
//        globals.finder = this;// finder 由Globals自行设置
        globals.baselib = this;
        env.set("_G", env);
        env.set("_VERSION", Lua._VERSION);
        env.set("assert", new _assert());
        env.set("collectgarbage", new collectgarbage());
//        env.set("dofile", new dofile());//TODO 裁剪掉for LuaView
        env.set("error", new error());
        env.set("getmetatable", new getmetatable());
        env.set("load", new load());
        env.set("loadfile", new loadfile());
        env.set("pcall", new pcall());
        env.set("print", new print(this));
        env.set("rawequal", new rawequal());
        env.set("rawget", new rawget());
        env.set("rawlen", new rawlen());
        env.set("rawset", new rawset());
        env.set("select", new select());
        env.set("setmetatable", new setmetatable());
        env.set("tonumber", new tonumber());
        env.set("tostring", new tostring());
        env.set("type", new type());
        env.set("xpcall", new xpcall());

        //extend for luaview
        new com.taobao.luaview.vm.extend.BaseLib(this, globals).extend(env);

        next next;
        env.set("next", next = new next());
        env.set("pairs", new pairs(next));
        env.set("ipairs", new ipairs());

        return env;
    }

    /**
     * ResourceFinder implementation
     * <p/>
     * Tries to open the file as a resource, which can work for JSE and JME.
     */
    public InputStream findResource(String filename) {
        return getClass().getResourceAsStream(filename.startsWith("/") ? filename : "/" + filename);
    }


    // "assert", // ( v [,message] ) -> v, message | ERR
    static final class _assert extends VarArgFunction {
        public Varargs invoke(Varargs args) {
            if (!args.arg1().toboolean())
                error(args.narg() > 1 ? args.optjstring(2, "assertion failed!") : "assertion failed!");
            return args;
        }
    }

    // "collectgarbage", // ( opt [,arg] ) -> value
    static final class collectgarbage extends VarArgFunction {
        public Varargs invoke(Varargs args) {
            String s = args.checkjstring(1);
            if ("collect".equals(s)) {
                System.gc();
                return ZERO;
            } else if ("count".equals(s)) {
                Runtime rt = Runtime.getRuntime();
                long used = rt.totalMemory() - rt.freeMemory();
                return varargsOf(valueOf(used / 1024.), valueOf(used % 1024));
            } else if ("step".equals(s)) {
                System.gc();
                return LuaValue.TRUE;
            } else {
                this.argerror("gc op");
            }
            return NIL;
        }
    }

    // "dofile", // ( filename ) -> result1, ...
    final class dofile extends VarArgFunction {
        public Varargs invoke(Varargs args) {
            args.argcheck(args.isstring(1) || args.isnil(1), 1, "filename must be string or nil");
            String filename = args.isstring(1) ? args.tojstring(1) : null;
            Varargs v = filename == null ?
                    loadStream(globals.STDIN, "=stdin", "bt", globals) :
                    loadFile(args.checkjstring(1), "bt", globals);
            return v.isnil(1) ? error(v.tojstring(2)) : v.arg1().invoke();
        }
    }

    // "error", // ( message [,level] ) -> ERR
    static final class error extends TwoArgFunction {
        public LuaValue call(LuaValue arg1, LuaValue arg2) {
            throw new LuaError(arg1.isnil() ? null : arg1.tojstring(), arg2.optint(1));
        }
    }

    // "getmetatable", // ( object ) -> table
    static final class getmetatable extends LibFunction {
        public LuaValue call() {
            return argerror(1, "value");
        }

        public LuaValue call(LuaValue arg) {
            LuaValue mt = arg.getmetatable();
            return mt != null ? mt.rawget(METATABLE).optvalue(mt) : NIL;
        }
    }

    // "load", // ( ld [, source [, mode [, env]]] ) -> chunk | nil, msg
    final class load extends VarArgFunction {
        public Varargs invoke(Varargs args) {
            LuaValue ld = args.arg1();
            args.argcheck(ld.isstring() || ld.isfunction(), 1, "ld must be string or function");
            String source = args.optjstring(2, ld.isstring() ? ld.tojstring() : "=(load)");
            String mode = args.optjstring(3, "bt");
            LuaValue env = args.optvalue(4, globals);
            return loadStream(ld.isstring() ? ld.strvalue().toInputStream() :
                    new StringInputStream(ld.checkfunction()), source, mode, env);
        }
    }

    // "loadfile", // ( [filename [, mode [, env]]] ) -> chunk | nil, msg
    final class loadfile extends VarArgFunction {
        public Varargs invoke(Varargs args) {
            args.argcheck(args.isstring(1) || args.isnil(1), 1, "filename must be string or nil");
            String filename = args.isstring(1) ? args.tojstring(1) : null;
            String mode = args.optjstring(2, "bt");
            LuaValue env = args.optvalue(3, globals);
            return filename == null ?
                    loadStream(globals.STDIN, "=stdin", mode, env) :
                    loadFile(filename, mode, env);
        }
    }

    // "pcall", // (f, arg1, ...) -> status, result1, ...
    final class pcall extends VarArgFunction {
        public Varargs invoke(Varargs args) {
            LuaValue func = args.checkvalue(1);
            if (globals != null && globals.debuglib != null)
                globals.debuglib.onCall(this);
            try {
                return varargsOf(TRUE, func.invoke(args.subargs(2)));
            } catch (LuaError le) {
                final String m = le.getMessage();
                return varargsOf(FALSE, m != null ? valueOf(m) : NIL);
            } catch (Exception e) {
                final String m = e.getMessage();
                return varargsOf(FALSE, valueOf(m != null ? m : e.toString()));
            } finally {
                if (globals != null && globals.debuglib != null)
                    globals.debuglib.onReturn();
            }
        }
    }

    // "print", // (...) -> void
    final class print extends VarArgFunction {
        final BaseLib baselib;

        print(BaseLib baselib) {
            this.baselib = baselib;
        }

        public Varargs invoke(Varargs args) {
            LuaValue tostring = globals.get("tostring");
            /*globals.STDOUT.print("[LuaView] ");
            for ( int i=1, n=args.narg(); i<=n; i++ ) {
                if ( i>1 ) globals.STDOUT.print( '\t' );
                LuaString s = tostring.call( args.arg(i) ).strvalue();
                globals.STDOUT.print(s.tojstring());
            }
            globals.STDOUT.println();*/
            StringBuffer sb = new StringBuffer();
            for ( int i=1, n=args.narg(); i<=n; i++ ) {
                if ( i>1 ) {
                    sb.append('\t');
                }
                LuaString s = tostring.call( args.arg(i) ).strvalue();
                sb.append(s.tojstring());
            }
            LogUtil.i(sb);
            return NONE;
        }
    }


    // "rawequal", // (v1, v2) -> boolean
    static final class rawequal extends LibFunction {
        public LuaValue call() {
            return argerror(1, "value");
        }

        public LuaValue call(LuaValue arg) {
            return argerror(2, "value");
        }

        public LuaValue call(LuaValue arg1, LuaValue arg2) {
            return valueOf(arg1.raweq(arg2));
        }
    }

    // "rawget", // (table, index) -> value
    static final class rawget extends LibFunction {
        public LuaValue call() {
            return argerror(1, "value");
        }

        public LuaValue call(LuaValue arg) {
            return argerror(2, "value");
        }

        public LuaValue call(LuaValue arg1, LuaValue arg2) {
            return arg1.checktable().rawget(arg2);
        }
    }


    // "rawlen", // (v) -> value
    static final class rawlen extends LibFunction {
        public LuaValue call(LuaValue arg) {
            return valueOf(arg.rawlen());
        }
    }

    // "rawset", // (table, index, value) -> table
    static final class rawset extends LibFunction {
        public LuaValue call(LuaValue table) {
            return argerror(2, "value");
        }

        public LuaValue call(LuaValue table, LuaValue index) {
            return argerror(3, "value");
        }

        public LuaValue call(LuaValue table, LuaValue index, LuaValue value) {
            LuaTable t = table.checktable();
            t.rawset(index.checknotnil(), value);
            return t;
        }
    }

    // "select", // (f, ...) -> value1, ...
    static final class select extends VarArgFunction {
        public Varargs invoke(Varargs args) {
            int n = args.narg() - 1;
            if (args.arg1().equals(valueOf("#")))
                return valueOf(n);
            int i = args.checkint(1);
            if (i == 0 || i < -n)
                argerror(1, "index out of range");
            return args.subargs(i < 0 ? n + i + 2 : i + 1);
        }
    }

    // "setmetatable", // (table, metatable) -> table
    static final class setmetatable extends LibFunction {
        public LuaValue call(LuaValue table) {
            return argerror(2, "value");
        }

        public LuaValue call(LuaValue table, LuaValue metatable) {
            final LuaValue mt0 = table.getmetatable();
            if (mt0 != null && !mt0.rawget(METATABLE).isnil())
                error("cannot change a protected metatable");
            return table.setmetatable(metatable.isnil() ? null : metatable.checktable());
        }
    }

    // "tonumber", // (e [,base]) -> value
    static final class tonumber extends LibFunction {
        public LuaValue call(LuaValue e) {
            return e.tonumber();
        }

        public LuaValue call(LuaValue e, LuaValue base) {
            if (base.isnil())
                return e.tonumber();
            final int b = base.checkint();
            if (b < 2 || b > 36)
                argerror(2, "base out of range");
            return e.checkstring().tonumber(b);
        }
    }

    // "tostring", // (e) -> value
    static final class tostring extends LibFunction {
        public LuaValue call(LuaValue arg) {
            LuaValue h = arg.metatag(TOSTRING);
            if (!h.isnil())
                return h.call(arg);
            LuaValue v = arg.tostring();
            if (!v.isnil())
                return v;
            return valueOf(arg.tojstring());
        }
    }

    // "type",  // (v) -> value
    static final class type extends LibFunction {
        public LuaValue call(LuaValue arg) {
            return valueOf(arg.typename());
        }
    }

    // "xpcall", // (f, err) -> result1, ...
    final class xpcall extends VarArgFunction {
        public Varargs invoke(Varargs args) {
            final LuaThread t = globals.running;
            final LuaValue preverror = t.errorfunc;
            t.errorfunc = args.checkvalue(2);
            try {
                if (globals != null && globals.debuglib != null)
                    globals.debuglib.onCall(this);
                try {
                    return varargsOf(TRUE, args.arg1().invoke(args.subargs(3)));
                } catch (LuaError le) {
                    final String m = le.getMessage();
                    return varargsOf(FALSE, m != null ? valueOf(m) : NIL);
                } catch (Exception e) {
                    final String m = e.getMessage();
                    return varargsOf(FALSE, valueOf(m != null ? m : e.toString()));
                } finally {
                    if (globals != null && globals.debuglib != null)
                        globals.debuglib.onReturn();
                }
            } finally {
                t.errorfunc = preverror;
            }
        }
    }

    // "pairs" (t) -> iter-func, t, nil
    static final class pairs extends VarArgFunction {
        final next next;

        pairs(next next) {
            this.next = next;
        }

        public Varargs invoke(Varargs args) {
            return varargsOf(next, args.checktable(1), NIL);
        }
    }

    // // "ipairs", // (t) -> iter-func, t, 0
    static final class ipairs extends VarArgFunction {
        inext inext = new inext();

        public Varargs invoke(Varargs args) {
            return varargsOf(inext, args.checktable(1), ZERO);
        }
    }

    // "next"  ( table, [index] ) -> next-index, next-value
    static final class next extends VarArgFunction {
        public Varargs invoke(Varargs args) {
            return args.checktable(1).next(args.arg(2));
        }
    }

    // "inext" ( table, [int-index] ) -> next-index, next-value
    static final class inext extends VarArgFunction {
        public Varargs invoke(Varargs args) {
            return args.checktable(1).inext(args.arg(2));
        }
    }

    /**
     * Load from a named file, returning the chunk or nil,error of can't load
     *
     * @param env
     * @param mode
     * @return Varargs containing chunk, or NIL,error-text on error
     */
    public Varargs loadFile(String filename, String mode, LuaValue env) {
        InputStream is = globals.getLuaResourceFinder().findResource(filename);//modify by song
        if (is == null)
            return varargsOf(NIL, valueOf("cannot open " + filename + ": No such file or directory"));
        try {
            return loadStream(is, "@" + filename, mode, env);
        } finally {
            try {
                is.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public Varargs loadStream(InputStream is, String chunkname, String mode, LuaValue env) {
        try {
            if (is == null)
                return varargsOf(NIL, valueOf("not found: " + chunkname));
            return globals.load(is, chunkname, mode, env);
        } catch (Exception e) {
            return varargsOf(NIL, valueOf(e.getMessage()));
        }
    }


    private static class StringInputStream extends InputStream {
        final LuaValue func;
        byte[] bytes;
        int offset, remaining = 0;

        StringInputStream(LuaValue func) {
            this.func = func;
        }

        public int read() throws IOException {
            if (remaining <= 0) {
                LuaValue s = func.call();
                if (s.isnil())
                    return -1;
                LuaString ls = s.strvalue();
                bytes = ls.m_bytes;
                offset = ls.m_offset;
                remaining = ls.m_length;
                if (remaining <= 0)
                    return -1;
            }
            --remaining;
            return bytes[offset++];
        }
    }
}
